package cn.evolution.retryutils.interceptor;

import cn.evolution.retryutils.annotations.LocalRetryable;
import cn.evolution.retryutils.pojo.entity.RetryInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author evolution
 * @title: LocalRetryInterceptor
 * @projectName retry-utils
 * @description: TODO
 * @date 2022-11-16 15:32
 */
@Slf4j
@Aspect
@Component
public class LocalRetryInterceptor  implements ApplicationContextAware {
    //重试队列映射关系（队列名  to 队列实例）
    private static final ConcurrentHashMap<String,List<RetryInfo>> RETRY_QUEUE_MAP = new ConcurrentHashMap<>();
    
    
    
    //重试任务的线程池
    @Autowired
    private  ThreadPoolTaskScheduler retryTaskScheduler;
    
    private ApplicationContext applicationContext;


    
    //初始化本地重试任务
    @PostConstruct
    public void init(){
        System.out.println("拦截器初始化");
        Reflections reflection = new Reflections("cn.evolution.retryutils", new MethodAnnotationsScanner());
        for(Method method :reflection.getMethodsAnnotatedWith(LocalRetryable.class)){
            LocalRetryable localRetryable = method.getAnnotation(LocalRetryable.class);
            long fixedDelay  = localRetryable.retryOnFixedDelay();
            String retryQueueName =localRetryable.retryQueueName();
            initSchedule(retryQueueName,fixedDelay);
            
        
        }
    }
    
    /**
     * 初始定时任务逻辑
     * @param retryQueueName
     * @param fixedDelay
     */
    private void initSchedule(String retryQueueName, long fixedDelay) {
//        List<RetryInfo> retryInfos = RETRY_QUEUE_MAP.getOrDefault(retryQueueName,new ArrayList<>());
 //       retryInfos.add(new RetryInfo());
        retryTaskScheduler.scheduleWithFixedDelay(()->{
          List<RetryInfo> retryInfoList = RETRY_QUEUE_MAP.get(retryQueueName);
          if(!CollectionUtils.isEmpty(retryInfoList)){
              for(RetryInfo retryInfo : retryInfoList){
                  try {
                      ReflectionUtils.findMethod(retryInfo.getClazz(),retryInfo.getMethod(),retryInfo.getParamTypes()).invoke(applicationContext.getBean(retryInfo.getClazz()),retryInfo.getArgs());
                  } catch (IllegalAccessException e) {
                    //  log.error(e.getMessage());
                  } catch (InvocationTargetException e) {
               //       log.error(e.getMessage());
                  }
                  retryInfoList.remove(retryInfo);
              }
          }
        },fixedDelay);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
     this.applicationContext =applicationContext;
    }
    
    
    /**
     * 切面、 异常之后，写入到队列中
     */
    @Around(value = "@annotation(cn.evolution.retryutils.annotations.LocalRetryable)")
    public Object around(ProceedingJoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method =signature.getMethod();
        LocalRetryable retryable = method.getAnnotation(LocalRetryable.class);
       // long delay = retryable.retryOnFixedDelay();
        String retryQueueName = retryable.retryQueueName();
        try{
            return joinPoint.proceed();
        }catch (Throwable e){
            log.error("切面异常",e.getMessage());
         RetryInfo retryInfo = new RetryInfo();
         retryInfo.setClazz(method.getDeclaringClass());
         retryInfo.setArgs(joinPoint.getArgs());
         retryInfo.setMethod(method.getName());
         retryInfo.setParamTypes(method.getParameterTypes());
         List<RetryInfo> retryInfoList = RETRY_QUEUE_MAP.getOrDefault(retryQueueName,new ArrayList<>());
         retryInfoList.add(retryInfo);
         RETRY_QUEUE_MAP.put(retryQueueName,retryInfoList);
         
        }
    
        return null;
    }
    
}
